#! /usr/bin/env python
"""
Interval shooting with PSRemote, using PSRemoteTest.exe
This Python program is based on the interval_shooting.pl Perl script
distributed with PSRemote.

Python version by Chip Chapin <cchapin@gmail.com>
12 March 2010
"""
import os
import re
import shlex
from subprocess import *
import sys
import time

# PSR_INSTALL_DIR = "C:\\Program Files\\BreezeSys\\PSRemote"
PSR_INSTALL_DIR = "C:\\Program Files (x86)\\BreezeSys\\PSRemote"
PSRTEST = PSR_INSTALL_DIR + "\\PSRemoteTest\\PSRemoteTest.exe"
PSRCMD = PSR_INSTALL_DIR + "\\PSRemote.exe"
PSR_SPAWN = PSR_INSTALL_DIR + "\\PSRemoteTest\\SpawnGuiApp.exe"

CAMERA_STARTUP_TIME = 10  # approx time in secs it takes to connect to camera and take photo

def _abort(message=None):
    """Print error message and exit."""
    if message:
        print "ERROR: %s" % message
    sys.exit(1)

def _log(message):
    print time.strftime("%Y-%m-%d %X  " + message)
    
def RunCommand(arglist):
    _log("RunCommand: " + str(arglist))
    result = Popen(arglist, stderr=STDOUT, stdout=PIPE).communicate()[0]
    result = result.rstrip()
    _log("-->" + result)
    return result    

def RunPSRemoteCommand(args=None):
    arglist = [PSRTEST]
    if (args):
        arglist.extend(shlex.split(args))
    _log("RunPSRemoteCommand: " + str(arglist))
    return RunCommand(arglist)

def ExitPSRemote():
    _log("Closing PSRemote ...")
    return RunPSRemoteCommand("-n -X")

def RestartPSRemote():
    ExitPSRemote();    # exit PSRemote just in case, then run it
    _log("Restarting PSRemote ...")
    sts = os.system("\"%s\" %s" % (PSR_SPAWN, PSRCMD))
    if sts:
        _abort("Failed to restart PSRemote")
    # TODO(cchapin): Replace use of os.system.
    # See example http://docs.python.org/library/subprocess.html
    # I'm not sure how to get the stdout and stderr in that case
    # Note that RunCommand does not work.
    #result = RunCommand([PSR_SPAWN, PSRCMD])
    #m = re.search("^Error", result)
    #if m:
    #    _abort("Failed to restart PSRemote: " + result)
    
def RunPSRemote():
    result = RunPSRemoteCommand("-n -q")
    if (result == ""):
        # it doesn't look as though PSRemote is running
        RestartPSRemote()

def ConnectToCamera():
    """Connect to camera and return camera model if successful."""
    result = RunPSRemoteCommand("-n -C 1 -q")
    camera = ""
    m = re.search("Camera model:\s+(.*)", result)
    if m:
        camera = m.group(1)
    else:
        # connection failed, try restarting PSRemote
        _log("Connection failed, restarting PSRemote...")
        RestartPSRemote()
        time.sleep(CAMERA_STARTUP_TIME)  # wait a while so that camera has time to connect
        time.sleep(10.0);

        # now see if we are connected
        _log("Check connection ...");
        result = RunPSRemoteCommand("-n -C 1 -q")
        m = re.search("Camera model:\s+(.*)", result)
        if m:
            camera = m.group(1)
        else:
            _abort("Failed connecting to camera: " + result)
    return camera

def DisconnectFromCamera():
    result = RunPSRemoteCommand("-n -C 0");
    if (result != ""):
        _abort("Error connecting to camera: " + result);

def TakeSinglePhoto(args=None):
    """Take a photo and return it's pathname if successful"""
    result = RunPSRemoteCommand(args)
    file = ""
    m = re.search("image saved as:\s+(.*)", result)
    if m:
        file = m.group(1)
    else:
        _log("Picture failure: " + result)
        # Ensure PSRemote is still running.
        RunPSRemote()
        # Try reconnecting to camera.
        camera = ConnectToCamera()
        _log("Reconnected to " + camera)
        result = RunPSRemoteCommand(args)
        file = ""
        m = re.search("image saved as:\s+(.*)", result)
        if m:
            file = m.group(1)
        else:
            _abort("Error taking picture: " + result)
    return file

def AutoBracket3():
    """Take a three shot autobracket sequence and return pathnames if successful"""
    # No exposure compensation.
    files = TakeSinglePhoto("-x 6")
    # +1 stop compensation
    files = "%s\n%s" % (files, TakeSinglePhoto("-x 3"))
    # -1 stop compensation
    files = "%s\n%s" % (files, TakeSinglePhoto("-x 9"))
    # set camera back to no exposure compensation
    RunPSRemoteCommand("-n -x 6")
    return files

def TakePhoto(bracket, camera):
    if (bracket):
        _log("Connected to %s, taking 3 shot auto-bracket sequence..." %
             camera)
        files = AutoBracket3()
        _log("Bracketed images:\n" + files)
    else:
        _log("Connected to %s, taking picture..." % camera)
        file = TakeSinglePhoto();
        _log("Image: " + file);

def TakePhotoAndDisconnect(bracket):
    _log("Connecting...")
    camera = ConnectToCamera();
    TakePhoto(bracket, camera)
    DisconnectFromCamera()
    _log("Disconnected from camera")
    

########################################################
# Main program
########################################################

#FIXME `cd $PSR_INSTALL_DIR`;

# Take pictures between these hours
# TODO(cchapin) make these command line args.
startHour = 7
startMinute = 00
endHour = 20
endMinute = 00
intervalInSecs = 3   # interval between shots (seconds)

bracket = 0   # set this to 1 to bracket the shots +/- one stop or 0 to take a single shot
remainConnected = 1  # Set this to 1 maintain camera connection between shots.

startTimeInSecs = 3600 * startHour + 60 * startMinute
endTimeInSecs = 3600 * endHour + 60 * endMinute
secsInDay = 3600 * 24
_log("Script started, shooting interval: %s seconds" % intervalInSecs)
_log("Running PSRemote")
RunPSRemote()
if remainConnected:
    camera = ConnectToCamera()
    cameraStartupTime = 0
else:
    cameraStartupTime = CAMERA_STARTUP_TIME
    
lastPhotoTime = -1.0
while (1):
    (year,mon,mday,hour,min,sec,wday,yday,isdst) = time.localtime()
    waitingTime = 0

    secsIntoDay = sec + 60 * (min + 60 * hour)
    if (secsIntoDay < startTimeInSecs):
        _log("Wait until start hour")
        waitingTime = startTimeInSecs - secsIntoDay - cameraStartupTime
    elif (secsIntoDay > endTimeInSecs):
        _log("After end hour, wait until start hour tomorrow")
        waitingTime = secsInDay + startTimeInSecs - secsIntoDay - cameraStartupTime
    elif (lastPhotoTime > 0):
        # Wait between photos
        nextPhotoTime = lastPhotoTime + intervalInSecs
        waitingTime = nextPhotoTime - time.time() - cameraStartupTime
        # waitingTime = intervalInSecs - (secsIntoDay % intervalInSecs) - cameraStartupTime
    if (waitingTime > 0):
        (year,mon,mday,hour,min,sec,wday,yday,isdst) = time.localtime(time.time() + waitingTime)
        msg = ("Waiting for %d secs... (until %02d/%02d/%02d %02d:%02d:%02d)" %
               (waitingTime, year, mon, mday, hour, min, sec))
        _log(msg);
        time.sleep(waitingTime)
    # Take the shot
    lastPhotoTime = time.time()
    if remainConnected:
        TakePhoto(bracket, camera)
    else:
        TakePhotoAndDisconnect(bracket)
        # These come from the Perl version.
        time.sleep(CAMERA_STARTUP_TIME)
        time.sleep(1)

ExitPSRemote()
_log("Script exiting")

